using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using RaisingStudio.SmallBasicCompiler;
using RaisingStudio.SmallProgram.Library;

namespace RaisingStudio.SmallBasicCompiler
{
    public class Compiler
    {
        private List<Assembly> _referenceAssemblies;
        private List<string> _libraryFiles = new List<string>();
        private List<Error> _errors = new List<Error>();

        private List<string> _references = new List<string>();
        public List<string> References
        {
            get
            {
                return this._references;
            }
        }
        private Parser _parser;
        public Parser Parser
        {
            get
            {
                return this._parser;
            }
        }

        private TypeInfoBag _typeInfoBag;
        public TypeInfoBag TypeInfoBag
        {
            get
            {
                return this._typeInfoBag;
            }
        }

        public Compiler()
        {
            this._parser = new Parser(this._errors);
            this._typeInfoBag = new TypeInfoBag();
            this.Initialize();
        }

        public void Initialize()
        {
            this.PopulateReferences();
            this.PopulateClrSymbols();
            this.PopulatePrimitiveMethods();
        }
        public List<Error> Compile(TextReader source)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            this._errors.Clear();
            this._parser.Parse(source);
            SemanticAnalyzer semanticAnalyzer = new SemanticAnalyzer(this._parser, this._typeInfoBag);
            semanticAnalyzer.Analyze();
            return this._errors;
        }
        public List<Error> Build(TextReader source, string outputName, string directory)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (outputName == null)
            {
                throw new ArgumentNullException("outputName");
            }
            if (directory == null)
            {
                throw new ArgumentNullException("directory");
            }
            this.Compile(source);
            if (this._errors.Count > 0)
            {
                return this._errors;
            }
            CodeGenerator codeGenerator = new CodeGenerator(this._parser, this._typeInfoBag, outputName, directory);
            codeGenerator.GenerateExecutable();
            this.CopyLibraryAssemblies(directory);
            return this._errors;
        }
        private void CopyLibraryAssemblies(string directory)
        {
            string location = typeof(Primitive).Assembly.Location;
            string fileName = Path.GetFileName(location);
            try
            {
                System.IO.File.Copy(location, Path.Combine(directory, fileName), true);
            }
            catch
            {
            }
            foreach (string current in this._libraryFiles)
            {
                try
                {
                    fileName = Path.GetFileName(current);
                    System.IO.File.Copy(current, Path.Combine(directory, fileName), true);
                }
                catch
                {
                }
            }
        }
        private void PopulateReferences()
        {
            this._referenceAssemblies = new List<Assembly>();
            this._referenceAssemblies.Add(typeof(Primitive).Assembly);
            foreach (string current in this.References)
            {
                try
                {
                    Assembly item = Assembly.LoadFile(current);
                    this._referenceAssemblies.Add(item);
                }
                catch
                {
                    throw new InvalidOperationException(string.Format(ResourceHelper.GetString("LoadReferenceFailed"), current));
                }
            }
        }
        private void PopulateClrSymbols()
        {
            foreach (Assembly current in this._referenceAssemblies)
            {
                this.AddAssemblyTypesToList(current);
            }
            string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            string path = Path.Combine(directoryName, "lib");
            if (Directory.Exists(path))
            {
                string[] files = Directory.GetFiles(path, "*.dll");
                for (int i = 0; i < files.Length; i++)
                {
                    string text = files[i];
                    try
                    {
                        Assembly assembly = Assembly.LoadFile(text);
                        this.AddAssemblyTypesToList(assembly);
                        this._libraryFiles.Add(text);
                    }
                    catch
                    {
                    }
                }
            }
        }
        private bool AddAssemblyTypesToList(Assembly assembly)
        {
            if (assembly == null)
            {
                return false;
            }
            bool result = false;
            Type[] types = assembly.GetTypes();
            for (int i = 0; i < types.Length; i++)
            {
                Type type = types[i];
                if (type.GetCustomAttributes(typeof(SmallProgramTypeAttribute), false).Length > 0 && type.IsVisible)
                {
                    this.AddTypeToList(type);
                    result = true;
                }
            }
            return result;
        }
        private void AddTypeToList(Type type)
        {
            TypeInfo typeInfo = new TypeInfo
            {
                Type = type,
                HideFromIntellisense = type.GetCustomAttributes(typeof(HideFromIntellisenseAttribute), false).Length > 0
            };
            MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public);
            for (int i = 0; i < methods.Length; i++)
            {
                MethodInfo methodInfo = methods[i];
                if (this.CanAddMethod(methodInfo) && !typeInfo.Methods.ContainsKey(methodInfo.Name.ToLower(CultureInfo.CurrentUICulture)))
                {
                    typeInfo.Methods.Add(methodInfo.Name.ToLower(CultureInfo.CurrentUICulture), methodInfo);
                }
            }
            new Dictionary<string, PropertyInfo>();
            PropertyInfo[] properties = type.GetProperties(BindingFlags.Static | BindingFlags.Public);
            for (int j = 0; j < properties.Length; j++)
            {
                PropertyInfo propertyInfo = properties[j];
                if (this.CanAddProperty(propertyInfo))
                {
                    typeInfo.Properties.Add(propertyInfo.Name.ToLower(CultureInfo.CurrentUICulture), propertyInfo);
                }
            }
            new Dictionary<string, EventInfo>();
            EventInfo[] events = type.GetEvents(BindingFlags.Static | BindingFlags.Public);
            for (int k = 0; k < events.Length; k++)
            {
                EventInfo eventInfo = events[k];
                if (this.CanAddEvent(eventInfo))
                {
                    typeInfo.Events.Add(eventInfo.Name.ToLower(CultureInfo.CurrentUICulture), eventInfo);
                }
            }
            if (typeInfo.Events.Count > 0 || typeInfo.Methods.Count > 0 || typeInfo.Properties.Count > 0)
            {
                this._typeInfoBag.Types[type.Name.ToLower(CultureInfo.CurrentUICulture)] = typeInfo;
            }
        }
        private bool CanAddMethod(MethodInfo methodInfo)
        {
            if (!methodInfo.IsGenericMethod && !methodInfo.IsConstructor && !methodInfo.ContainsGenericParameters && !methodInfo.IsSpecialName && (methodInfo.ReturnType == typeof(void) || methodInfo.ReturnType == typeof(Primitive)))
            {
                ParameterInfo[] parameters = methodInfo.GetParameters();
                for (int i = 0; i < parameters.Length; i++)
                {
                    ParameterInfo parameterInfo = parameters[i];
                    if (parameterInfo.ParameterType != typeof(Primitive))
                    {
                        return false;
                    }
                }
                return true;
            }
            return false;
        }
        private bool CanAddProperty(PropertyInfo propertyInfo)
        {
            return !propertyInfo.IsSpecialName && propertyInfo.PropertyType == typeof(Primitive);
        }
        private bool CanAddEvent(EventInfo eventInfo)
        {
            return !eventInfo.IsSpecialName && eventInfo.EventHandlerType == typeof(SmallProgramCallback);
        }
        private void PopulatePrimitiveMethods()
        {
            Type typeFromHandle = typeof(Primitive);
            this._typeInfoBag.StringToPrimitive = typeFromHandle.GetMethod("op_Implicit", new Type[]
			{
				typeof(string)
			});
            this._typeInfoBag.NumberToPrimitive = typeFromHandle.GetMethod("op_Implicit", new Type[]
			{
				typeof(double)
			});
            this._typeInfoBag.PrimitiveToBoolean = typeFromHandle.GetMethod("ConvertToBoolean");
            this._typeInfoBag.Negation = typeFromHandle.GetMethod("op_UnaryNegation");
            this._typeInfoBag.Add = typeFromHandle.GetMethod("op_Addition");
            this._typeInfoBag.Subtract = typeFromHandle.GetMethod("op_Subtraction");
            this._typeInfoBag.Multiply = typeFromHandle.GetMethod("op_Multiply");
            this._typeInfoBag.Divide = typeFromHandle.GetMethod("op_Division");
            this._typeInfoBag.GreaterThan = typeFromHandle.GetMethod("op_GreaterThan");
            this._typeInfoBag.GreaterThanOrEqualTo = typeFromHandle.GetMethod("op_GreaterThanOrEqual");
            this._typeInfoBag.LessThan = typeFromHandle.GetMethod("op_LessThan");
            this._typeInfoBag.LessThanOrEqualTo = typeFromHandle.GetMethod("op_LessThanOrEqual");
            this._typeInfoBag.EqualTo = typeFromHandle.GetMethod("op_Equality", new Type[]
			{
				typeof(Primitive), 
				typeof(Primitive)
			});
            this._typeInfoBag.NotEqualTo = typeFromHandle.GetMethod("op_Inequality", new Type[]
			{
				typeof(Primitive), 
				typeof(Primitive)
			});
            this._typeInfoBag.And = typeFromHandle.GetMethod("op_And", new Type[]
			{
				typeof(Primitive), 
				typeof(Primitive)
			});
            this._typeInfoBag.Or = typeFromHandle.GetMethod("op_Or", new Type[]
			{
				typeof(Primitive), 
				typeof(Primitive)
			});
            this._typeInfoBag.GetArrayValue = typeFromHandle.GetMethod("GetArrayValue");
            this._typeInfoBag.SetArrayValue = typeFromHandle.GetMethod("SetArrayValue");
        }
    }
}
